#include<stdio.h>
#include<string.h>
#include<fcntl.h>
#include<termios.h>

#include <openssl/bio.h>
#include <openssl/ssl.h>
#include <openssl/err.h>


struct recbuf
{
	char buf[32768];
	int len;
	int filelen;
};


struct IPGWcmd
{
	char uid[32];
	char rawpasswd[32];
	char operation[16];
	int range;
	int timeout;
};

int getcommand(struct IPGWcmd *,int,char **);
int httprequest(char *method,char *path,char *data,struct recbuf *);
int searchhtml(char *buf,int len,char *elem,char *prop,char *value);
void Valid(int r,struct recbuf *);
int spacechar(char);

BIO * bio;
SSL * ssl;
int connected;
char ipgwserver[32]="ipgw.pku.edu.cn:https";

int main(int argc,char *argv[])
{
	struct IPGWcmd ipgwcmd;
	struct recbuf html;
	SSL_CTX *ctx;
	X509 *peer;
	char peer_CN[32];
	unsigned char remote_ip[4];
	int i,r,len,inhtml;
	char tmp[32768],password[128],postdata[256],postpath[128];

	
	if(!getcommand(&ipgwcmd,argc,argv))
	{
		printf("Usage: foxipgw [-u username] [-s server] [-8] -c|d|D\n"
			   "  -c   connect\n"
			   "  -d   disconnect\n"
			   "  -D   disconnect all\n"
			   "  -8   set timeout to 8 hours\n");
		return 1;
	}

	password[0]=0;
	for(i=0;i<32;i++)
	{
		sprintf(tmp,"%%%02X",ipgwcmd.rawpasswd[i]);
		if(tmp[1]>'1')
			strcat(password,tmp);
		else
			break;
	}
	sprintf(postdata,"uid=%s&password=%s&operation=%s&range=%d&timeout=%d",
			ipgwcmd.uid,password,ipgwcmd.operation,ipgwcmd.range,ipgwcmd.timeout);

	OpenSSL_add_all_algorithms();

	ctx = SSL_CTX_new(SSLv3_client_method());
	bio = BIO_new_ssl_connect(ctx);
	BIO_get_ssl(bio, & ssl);
	SSL_set_mode(ssl, SSL_MODE_AUTO_RETRY);

	BIO_set_conn_hostname(bio, ipgwserver);

	/* start communication */
	
	if(BIO_do_connect(bio) <= 0)
	{
		printf("Cound not connect to server!\n");
		return 2;
	}
	memcpy(remote_ip,BIO_get_conn_ip(bio),4);
	sprintf(tmp,"%d.%d.%d.%d",remote_ip[0],remote_ip[1],remote_ip[2],remote_ip[3]);
	peer=SSL_get_peer_certificate(ssl);
	X509_NAME_get_text_by_NID(X509_get_subject_name(peer),NID_commonName, peer_CN, 256);
	if(strcmp(peer_CN,tmp))
	{
		printf("Invalid certificate! Abort!\n");
		return 2;
	}
	connected=1;
	printf("Server IP: %s\n",tmp);
	sprintf(ipgwserver,"%s:https",tmp);

	html.len=32767;
	
	printf("Requesting / ... ");
	Valid( httprequest("GET","/",NULL,&html), &html );
	printf("OK\n");
	for(i=0;i<html.filelen-1;)
	{
		r=searchhtml(html.buf+i,html.filelen-i,"FRAME","name",tmp);
		if(r==-1)
		{
			printf("Error detecting frame\n");
			return 3;
		}
		if(strcmp(tmp,"main")==0) /* found */
		{
			if(searchhtml(html.buf+r+i,html.filelen-r-i,"FRAME","src",tmp)!=0)
			{
				printf("No main page!\n");
				return 3;
			}
			break;
		}
		i+=r+1;
	}
	
	printf("Requesting for %s ... ",tmp);
	Valid( httprequest("GET",tmp,NULL,&html), &html);
	printf("OK\n");
	r=searchhtml(html.buf,html.filelen,"FORM","action",tmp);
	if(r==-1)
	{
		printf("Error detecting form\n");
		return 3;
	}
	strcpy(postpath,tmp);

	len=0;
	for(i=0;i<html.filelen-1 && len<3;)
	{
		r=searchhtml(html.buf+i,html.filelen-i,"input","type",tmp);
		if(r==-1)
		{
			printf("No text input found in form\n");
			return 3;
		}
		if(strcmp(tmp,"text")==0)
		{
			if(searchhtml(html.buf+r+i,html.filelen-r-i,"input","name",tmp)!=0)
			{
				printf("Hmm..., where is name?\n");
				return 3;
			}
			if(strcmp(tmp,"uid")!=0)
			{
				printf("Format changed! Need update?\n");
				return 3;
			}
			len|=1;
		}
		else 
			if(strcmp(tmp,"password")==0)
			{
				if(searchhtml(html.buf+r+i,html.filelen-r-i,"input","name",tmp)!=0)
				{
					printf("Hmm..., where is name?\n");
					return 3;
				}
				if(strcmp(tmp,"password")!=0)
				{
					printf("Format changed! Need update?\n");
					return 3;
				}
				len|=2;
			}
		i+=r+1;
	}

	printf("Sending infomation to %s ...",postpath);
	Valid( httprequest("POST",postpath,postdata,&html), &html);
	printf("Done!\n");

	BIO_free_all(bio);
	SSL_CTX_free(ctx);
	
	r=searchhtml(html.buf,html.filelen,"TABLE","",tmp);
	if(r==-1)
	{
		printf("TABLE not found, discover by yourself.\n");
		html.buf[html.filelen]=0;
		printf("%s\n",html.buf);
		return 0;
	}
	len=0;
	for(i=r;i<html.filelen;i++)
	{
		if(html.buf[i]=='>')
		  { inhtml=0; continue; }
		if(html.buf[i]=='<')
		  {	inhtml=1; continue; }
		if(inhtml)
		{
			if(strncasecmp(html.buf+i-1,"<br>",4)==0)
			{
				tmp[len]='\n';
				len++;
				continue;
			}
			if(strncasecmp(html.buf+i-1,"<tr>",4)==0)
			{
				tmp[len]='\n';
				len++;
				continue;
			}
			if(strncasecmp(html.buf+i-1,"</table>",8)==0)
			{
				tmp[len]='\0';
				break;
			 }
		}
		else
		{
			if(strncasecmp(html.buf+i,"&nbsp;",6)==0)
			{
				tmp[len]=' ';
				while(html.buf[i]!=';') i++;
				continue;
			}
			if((unsigned)html.buf[i]>=32)
			{
				tmp[len]=html.buf[i];
				len++;
			}
		}
	}
	
	printf("%s\n",tmp);
	return 0;
	
}



int httprequest(char *method,char *path,char *data,struct recbuf *recv)
{
	char httphead[1024];
	char *httpdummy="HTTP/1.1\r\n"
 "Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/x-shockwave-flash, */*\r\n"
 "Accept-Language: zh-cn\r\n"
 "Content-Type: application/x-www-form-urlencoded\r\n"
 "Accept-Encoding: gzip, deflate\r\n"
 "User-Agent: Mozilla/4.0 (compatible; MSIE 5.0; Windows 98; DigExt)\r\n"
 "Host: ipgw.pku.edu.cn\r\n";
	char tmp[32];
	int post=0;
	int len,r,datalen;
	char inbuf[1024];
	char filename[256];
	int i,pos=0,start=0,bytes=0;
	int head_complete=0,over=0,formaterr=0,skip=0;
	int status=-1,length=-1;
	int connclose=0;
	FILE *fplog;

	if(connected==0)
	{
		printf("Connection closed, reconnect ...\n");
		BIO_set_conn_hostname(bio, ipgwserver);
		if(BIO_do_connect(bio) <= 0)
			return -1;
		connected=1;
/*	memcpy(remote_ip,BIO_get_conn_ip(bio),4);
	printf("IP %d.%d.%d.%d\n",remote_ip[0],remote_ip[1],remote_ip[2],remote_ip[3]);
*/	}

	if(strcmp("POST",method)==0)
		post=1;
	if(path[0]!='/')
		sprintf(httphead,"%s /%s %s",method,path,httpdummy);
	else
		sprintf(httphead,"%s %s %s",method,path,httpdummy);
	if(post && data)
	{
		datalen=strlen(data);
		sprintf(tmp,"Content-Length: %d\r\n",datalen);
		strcat(httphead,tmp);
	}
	strcat(httphead,"\r\n");
	len=strlen(httphead);
	r=SSL_write(ssl,httphead,len);
	if(SSL_get_error(ssl,r)!=SSL_ERROR_NONE || r!=len)
		return -1;
	if(post)
	{
		r=SSL_write(ssl,data,datalen);
		if(SSL_get_error(ssl,r)!=SSL_ERROR_NONE || r!=datalen)
			return -1;
	}
/*************************** now read the reply ***************************/

	for(;;)
	{
		r=SSL_read(ssl,inbuf+pos,1023-pos);
		switch(SSL_get_error(ssl,r))
		{
			case SSL_ERROR_NONE:
				break;
			case SSL_ERROR_ZERO_RETURN:
			case SSL_ERROR_SYSCALL:
				over=1;
				break;
			default:
				return -1;
		}
		if(over)
			break;
		pos+=r;
		if(!head_complete)
			for(i=start;i<pos;i++)
			{
				if(inbuf[i]=='\n')
				{
					if(i==start || inbuf[i-1]!='\r')  /* Invalid head line */
					  {   formaterr=1; 	break;	}
					if(status==-1)  /* the first line */
					{
						if(strncmp("HTTP/1.",inbuf+start,7)!=0) /* must begin with this */
						  { 	formaterr=1;	break;	}
						if(sscanf(inbuf+start+9,"%d",&status)!=1) /* read status */
						  {		formaterr=1;	break;	}
					}
					else  /* processing http header */
					{
						if(i-1==start) /* blank line, means end of http header */
						{
							  head_complete=1;
							  recv->filelen=length;
							  start=i+1;
							  break;
						}
						if(strncmp("Connection: close",inbuf+start,17)==0)
							connclose=1;
						if(strncasecmp("Content-Length: ",inbuf+start,16)==0)
						{
							if(sscanf(inbuf+start+16,"%d",&length)!=1) /* read length */
							  {		formaterr=1;	break;	}
						}
					}
					start=i+1;
				}  /* one line processed */
			} /* end of loop */
		if(formaterr)
		{
			inbuf[pos]=0;
			strncpy(recv->buf,inbuf,recv->len);
			return 0;
		}
		if(head_complete)
		{
			if(length==-1 && status!=200) /* did not specify Content-Length */
			{
				inbuf[pos]=0;
				strncpy(recv->buf,inbuf,recv->len);
				recv->filelen=-1;
				break;
			}
			if(bytes+pos-start>recv->len)  /* buffer not enough */
			{
				if(skip==0)
				{
					memcpy(recv->buf+bytes,inbuf+start,recv->len-bytes);
					skip=1;
				}
			}
			else
				memcpy(recv->buf+bytes,inbuf+start,pos-start);
			bytes+=pos-start;  /* size already read */
			if(length>0 && bytes>=length)
			{	over=1;  break; }
			pos=0;
			start=0;
		}
	}
	if(connclose)
	{
/*		SSL_shutdown(ssl); */
		connected=0;
		BIO_reset(bio);
	}

	if(length==-1 && status==200)
		recv->filelen=bytes;
	return status;
}



int getcommand(struct IPGWcmd *cmd, int argc, char *argv[])
{
	struct termios ta0,ta;
	int fd;
	int inuid,insrv,mode;
	int i;
	
	cmd->timeout=0;    /* default: 20 min */
	strcpy(cmd->uid,"10601001");
	strcpy(cmd->rawpasswd,"pku");
	if(argc<2)
		return 0;
	inuid=0;  insrv=0;
	for(i=1;i<argc;i++)
	{
		if(argv[i][0]=='-')
			switch(argv[i][1])
			{
				case 'c': cmd->range=2;  
						  strcpy(cmd->operation,"connect");
						  break;
				case 'd': cmd->range=3;
						  strcpy(cmd->operation,"disconnect");
						  break;
				case 'D': cmd->range=4;
						  strcpy(cmd->operation,"disconnectall");
						  break;
				case 'u': inuid=1;
						  break;
				case 's': insrv=1;
						  break;
				case '8': cmd->timeout=1;
						  break;
     			default:  printf("Invalid option -%c\n",argv[i][1]);
						  return 0;
			}
		else
		{
			if(inuid)
			{
				strncpy(cmd->uid,argv[i],31);
				inuid=0;
			}
			else if(insrv)
			{
				 snprintf(ipgwserver,31,"%s:https",argv[i]);
				 insrv=0;
			}
			else
			{
				printf("Bad argument.\n");
				return 0;
			}
		}
	}    /* end of command line scan */
	if(inuid)
	{
		printf("Missing USERNAME with -u option\n");
		return 0;
	}
	if(insrv)
	{
		printf("Missing SERVER_IP with -s option\n");
		return 0;
	}

	if(cmd->range==2 || cmd->range==4)
	{
		fd=open("/dev/tty",O_RDWR);
		write(fd,"Enter password:",15);
		tcgetattr(fd,&ta0);
		ta=ta0;
		ta.c_lflag&=(~ECHO);
		tcsetattr(fd,TCSANOW,&ta);
		read(fd,cmd->rawpasswd,32);
		tcsetattr(fd,TCSANOW,&ta0);
		close(fd);
		fflush(stdin);
	}
	return 1;
}


int searchhtml(char *buf,int len,char *elem,char *prop,char *value)
{
	int incomment=0,inquote=0,inelem=0,invalue=0;
	int test=0,wait,gotvalue,check,matchpos;
	int elemlen,proplen,quoted;
	int index=0,i;

	elemlen=strlen(elem);
	if(prop)
		proplen=strlen(prop);
	else
		proplen=0;

	for(i=0;i<len;i++)
	{
		if(invalue)
		{
			if(buf[i]=='\"')
			{
				if(index==0)
					quoted=1;
				else
				{
					if(quoted)
					{
						value[index]=0;
						gotvalue=1;
					}
					else
						inquote=1;
					invalue=0;
					wait=1;
				}
				continue;
			}
			if(quoted || !(spacechar(buf[i]) || buf[i]=='>') )
			{
				value[index]=buf[i];
				index++;
				continue;
			}
			else  /* unquoted, end of value */
			{
				value[index]=0;
				gotvalue=1;
				invalue=0;
			}  /* go on processing */
		}
		if(incomment)  /* ignore everything */
		{
			if(buf[i]=='>' && buf[i-1]=='-' && buf[i-2]=='-')
			  incomment=0;
			continue;
		}
		if(inquote)
		{
			if(buf[i]=='\"')
				inquote=0;
			continue;
		}
		if(inelem)
		{
			if(test)  /* find property */
			{
				if(spacechar(buf[i]))
					{  wait=0; check=1; continue;  }
				if(buf[i]=='>')
					if(gotvalue || proplen==0)
						return matchpos;
				if(wait) /* space required but not met, give up */
					{  test=0;  check=0; }
				if(check && proplen>0 && gotvalue==0) 
				{
					if(i+proplen<len-1 && strncasecmp(buf+i,prop,proplen)==0 && buf[i+proplen]=='=')
					{  /* property name match, now get value */
						i+=proplen;
						invalue=1;
						quoted=0;
						continue;
					}
					else
						check=0;
				}
			}
			if(buf[i]=='>')
				inelem=0;
			if(buf[i]=='\"')
				inquote=1;
			continue;
		}

		/* plain text */
		if(buf[i]=='<') /* HTML element start */
		{
			if(i+3<len && strncmp(buf+i,"<!--",4)==0)
				incomment=1;
			else
			{
				inelem=1;
				if(i+elemlen<len-1 && strncasecmp(buf+i+1,elem,elemlen)==0) /* partial match */
				{
					test=1;
					wait=1;
					gotvalue=0;
					matchpos=i;
					i+=elemlen;
				}
			}
		}
	}
	return -1;  /* not found */
}


void Valid(int r,struct recbuf *html)
{
	if(r==-1)
	{
		printf("Cannot process request!\n");
		exit(2);
	}
	if(r==0)
	{
		printf("Bad reply!\n");
		exit(2);
	}
	if(r!=200)
	{
		printf("HTTP %d ,error\n",r);
		exit(2);
	}

	if(html->filelen>html->len)
	{
		printf("HTML page too big, I cannot handle.\n");
		exit(3);
	}
}


int spacechar(char c)
{
	if(c==' ' || c=='\t' || c=='\r' || c=='\n')
		return 1;
	else
		return 0;
}


